// 向量对象....................................................................................................................................................
function Vector(point){
	this.x = point.x;
	this.y = point.y;
}

Vector.prototype = {
	// 向量的模
	getMagnitude: function(){
		return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
	},

	// 向量相加
	add: function(vector){
		var v = new Vector();

		v.x = this.x + vector.x;
		v.y = this.y + vector.y;

		return v;
	},

	// 向量相减
	subtract: function(vector){
		var v = new Vector();

		v.x = this.x - vector.x;
		v.y = this.y - vector.y;

		return v;
	},

	// 点积
	dotProduct: function(vector){
		return this.x * vector.x + this.y * vector.y;
	},

	// 边界
	edge: function(vector){
		return this.subtract(vector);
	},

	// 获得与之相对的垂直向量
	perpendicular: function(){
		var v = new Vector();

		v.x = this.y;
		v.y = 0 - this.x;

		return v;
	},

	// 获取单位向量
	normalize: function(){
		var v = new Vector(),
			m = this.getMagnitude;

		if(m != 0){
			v.x = this.x / m;
			v.y = this.y / m;
		}

		return v;
	},

	// 获取垂直向量的单位向量
	normal: function(){
		var p = this.perpendicular();

		return p.normalize();
	}
};

// 投影对象.........................................................................................................................................................
function Projection(min, max){
	this.min = min;
	this.max = max;
}

Projection.prototype = {
	// 判断投影是否重叠
	overlaps: function(projection){
		return this.max > projection.min && this.min < projection.max;
	}
};

// 利用分离轴定理与最小偏移向量

// 形状..............................................................................................................................................................
function Shape(){
	this.x = undefined;
	this.y = undefined;
	this.strokeStyle = 'rgba(255, 253, 208, 0.9)';
	this.fillStyle = 'rgba(147, 197, 114, 0.8)';
}

Shape.prototype = {
	// 碰撞函数
	collidesWith: function(otherShape){
		var axes = this.getAxes().concat(otherShape.getAxes()); // 轴线

		return !this.separationOnAxes(axes, otherShape);
	},

	// 最小平移向量
	minimumTransformVector: function(axes, shape){
		var minimumOverlap = 10000,
			overlap,
			axisWithSmallestOverlap;

		for(var i = 0; i < axes.length; i++){
			var axis = axes[i],
				projection1 = shape.project(axis);
				projection2 = this.project(axis);

			overlap = projection1.overlaps(projection2);

			if(!overlap){
				return {
					axis: undefined, // 没有碰撞
					overlap: 0
				};
			}else{
				if(overlap < minimumOverlap){
					minimumOverlap = overlap;
					axisWithSmallestOverlap = axis;
				}
			}
		}

		return {
			axis: axisWithSmallestOverlap, // 受测图形重叠部分长度最小的轴
			overlap: minimumOverlap // 受测图形重叠部分最小长度
		};
	},

	// 分离轴
	separationOnAxes: function(axes, otherShape){
		for(var i = 0; i < axes.length; i++){
			var axis = axes[i],
				projection1 = otherShape.project(axis);
				projection2 = this.project(axis);
			
			if(!projection1.overlaps(projection2)){
				return true; // 尽早退出，没有必要测试剩余的轴
			}
		}

		return false;
	},

	// 获取分离轴
	getAxes: function(){
		throw 'getAxes() not implemented.'; // 抛出异常，该方法尚未实现
	},

	// 投影
	project: function(axis){
		throw 'project(axis) not implemented.';
	},

	// 移动
	move: function(dx, dy){
		throw 'move(dx, dy) not implemented.'
	},

// 绘制方法.......................................................................
	// 创建路径
	createPath: function(context){
		throw 'createPath(context) not implemented.'
	},

	// 填充
	fill: function(context){
		context.save();
		context.fillStyle = this.fillStyle;
		this.createPath(context);
		context.fill();
		context.restore();
	},

	// 描边
	stroke: function(context){
		context.save();
		context.strokeStyle = this.strokeStyle;
		this.createPath(context);
		context.stroke();
		context.restore();
	},

	// 判断当前点是否在路径中
	isPointInPath: function(context, x, y){
		this.createPath(context);
		return context.isPointInPath(x, y);
	}

};

// 点对象......................................................................................................................................................
function Point(x, y){
	this.x = x;
	this.y = y;
}

// 多边形对象...................................................................................................................................................
function Polygon(){
	this.points = [];
	this.strokeStyle = 'blue';
	this.fillStyle = 'white';
}

// 多边形继承形状的属性
Polygon.prototype = new Shape();

// 重构多边形碰撞函数
Polygon.prototype.collidesWith = function(shape){
	var axes = shape.getAxes();

	if(axes === undefined){
		return polygonCollidesWithCircle(this, shape);
	}else{
		axes.concat(this.getAxes());

		return !this.separationOnAxes(axes, shape);
	}
};

Polygon.prototype.collidesWithNew = function(shape){
	if(shape.radius !== undefined){
		return polygonCollidesWithCircle(this, shape);
	}else{
		return polygonCollidesWithPolygon(this, shape);
	}
};

// Projects each point in the polygon onto the specified axis and then returns a projection width the minimum and maximum of those projected points.
// 投影
Polygon.prototype.project = function(axis){
	var scalars = [],
		v = new Vector();

	this.points.forEach(function(point){
		v.x = point.x;
		v.y = point.y;
		scalars.push(v.dotProduct(axis));
	});

	return new Projection(Math.min.apply(Math, scalars), Math.max.apply(Math, scalars));
};

// Returns all of the polygon's axes needed for collision detection testing with SAT
// 获取所有的分离轴
Polygon.prototype.getAxes = function(){
	var v1 = new Vector(),
		v2 = new Vector(),
		axes = [];

	for(var i = 0; i < this.points.length - 1; i++){
		v1.x = this.points[i].x;
		v1.y = this.points[i].y;

		v2.x = this.points[i + 1].x;
		v2.y = this.points[i + 1].y;

		axes.push(v1.edge(v2).normal());
	}

	v1.x = this.points[this.points.length - 1].x;
	v1.y = this.points[this.points.length - 1].y;

	v2.x = this.points[0].x;
	v2.y = this.points[0].y;

	axes.push(v1.edge(v2).normal());

	return axes;
};

// 添加连接点
Polygon.prototype.addPoint = function(x, y){
	this.points.push(new Point(x, y));
};

// 重写了Shape的createPath方法
Polygon.prototype.createPath = function(context){
	if(this.points.length === 0) return;

	context.beginPath();
	context.moveTo(this.points[0].x, this.points[0].y);
	for(var i = 1; i < this.points.length - 1; i++){
		context.lineTo(this.points[i].x, this.points[i].y);
	}
	context.closePath();
};

// 移动
Polygon.prototype.move = function(dx, dy){
	// for(var i = 0, point; i < this.points.length; i++){
	for(var i = 0, point; i < this.points.length; ++i){
		point = this.points[i];
		point.x += dx;
		point.y += dy;
	}
};


// 圆形..............................................................................................................................................................................
function Circle(x, y, radius){
	this.x = x;
	this.y = y;
	this.radius = radius;
	this.strokeStyle = 'rgba(255, 253, 208, 0.9)';
	this.fillStyle = 'rgba(147, 197, 114, 0.8)';
}

Circle.prototype = new Shape();

Circle.prototype.collidesWith = function(shape){
	var axes = shape.getAxes(),
		distance;

	if(axes === undefined){ // 判断为圆
		distance = Math.sqrt(Math.pow(shape.x - this.x, 2) + Math.pow(shape.y - this.y, 2));

		return distance < Math.abs(this.radius + shape.radius);
	}else{ // 判断为多边形
		return polygonCollidesWithCircle(shape, this);
	}	
};


Circle.prototype.collidesWithNew = function(shape){
	if(shape.radius === undefined){
		return polygonCollidesWithCircle(shape, this);
	}else{
		return circleCollidesWithCircle(shape, this);
	}
};

Circle.prototype.getAxes = function(){
	return undefined; // 圆有无限条投影
};

Circle.prototype.project = function(axis){
	var scalars = [],
		point = new Point(this.x, this.y),
		dotProduct = new Vector(point).dotProduct(axis);

	scalars.push(dotProduct);
	scalars.push(dotProduct + this.radius);
	scalars.push(dotProduct - this.radius);

	return new Projection(Math.min.apply(Math, scalars), Math.max.apply(Math, scalars));
};

Circle.prototype.move = function(dx, dy){
	this.x += dx;
	this.y += dy;
};

Circle.prototype.createPath = function(context){
	context.beginPath();
	context.arc(this.x, this.y, this.radius, 0, Math.PI*2, false);
};

// 检测多边形与圆之间的碰撞
function getPolygonPointClosestToCircle(polygon, circle){
	var min = 10000,
		length,
		testPoint,
		closestPoint;

	for(var i = 0; i < polygon.points.length; i++){
		testPoint = polygon.points[i];
		length = Math.sqrt(Math.pow(testPoint.x - circle.x, 2) + Math.pow(testPoint.y - circle.y, 2));

		if(length < min){
			min = length;
			closestPoint = testPoint;
		}

	}

	return closestPoint;
}

function polygonCollidesWithCircle(polygon, circle){
	var min = 10000,
		v1, 
		v2,
		edge,
		perpendicular,
		normal,
		axes = polygon.getAxes(),
		closestPoint = getPolygonPointClosestToCircle(polygon, circle);

	v1 = new Vector(new Point(circle.x, circle.y));
	v2 = new Vector(new Point(closestPoint.x, closestPoint.y));
	
	axes.push(v1.subtract(v2).normalize());

	return !polygon.separationOnAxes(axes, circle);
}

// 图像形状.............................................................................................................................................................................
function ImageShape(imageSource, x, y, width, height){
	var self = this;

	this.image = new Image();
	this.imageLoaded = false;
	this.points = [new Point(x, y)];
	this.x = x;
	this.y = y;

	this.image.src= imageSource;
	this.image.addEventListener('load', function(e){
		self.setPolygonPoints();
		self.imageLoaded = true;
	}, false);
}

ImageShape.prototype = new Polygon();

ImageShape.prototype.fill = function(context){
	// Nothing to do.
};

ImageShape.prototype.setPolygonPoints = function(){
	this.points.push(new Point(this.x + this.image.width, this.y));
	this.points.push(new Point(this.x + this.image.width, this.y + this.image.height));
	this.points.push(new Point(this.x, this.y + this.image.height));
};

ImageShape.prototype.drawImage = function(context){
	context.drawImage(this.image, this.points[0].x, this.points[0].y);
};

ImageShape.prototype.stroke = function(context){
	var self = this;

	if(this.imageLoaded){
		this.drawImage(context);
	}else{
		this.image.addEventListener('load', function(e){
			self.drawImage(context);
		}, false);
	}
};

// 精灵形状..............................................................................................................................................................................
function SpriteShape(sprite, x, y){
	this.sprite = sprite;
	this.x = x;
	this.y = y;

	sprite.left = x;
	sprite.top = y;
	
	this.setPolygonPoints();
}

SpriteShape.prototype = new Polygon();

SpriteShape.prototype.move = function(dx, dy){
	var point,
		x;

	for(var i = 0; i < this.points.length; i++){
		point = this.points[i];
		point.x += dx;
		point.y += dy;
	}

	this.sprite.left = this.points[0].x;
	this.sprite.top = this.points[0].y;
};

SpriteShape.prototype.fill = function(context){
	// Nothing to do.
};

SpriteShape.prototype.setPolygonPoints = function(){
	this.points.push(new Point(this.x, this.y));
	this.points.push(new Point(this.x + this.sprite.width, this.y));
	this.points.push(new Point(this.x + this.sprite.width, this.y + this.sprite.height));
	this.points.push(new Point(this.x, this.y + this.sprite.height));
};

SpriteShape.prototype.stroke = function(context){
	this.sprite.paint(context);
};

// 最小平移向量...........................................................................................................................................................................
/*
	axis--轴，是一个指示方向的单位向量
	overlap--是指两个图形的重叠部分，是一个标量
*/
function MinimumTranslationVector(axis, overlap){
	this.axis = axis;
	this.overlap = overlap;
}

// Collisions.............................................................................................................................................................................
	
	// Collison between two polygons（两个多边形的碰撞）
	function polygonCollidesWithPolygon(p1, p2){
		var mtv1 = p1.minimumTransformVector(p1.getAxes(), p2),
			mtv2 = p2.minimumTransformVector(p2.getAxes(), p1);

		if(mtv1.overlap === 0 && mtv2.overlap === 0){
			return {
				axis: undefined,
				overlap: 0
			};
		}else{
			return mtv1.overlap < mtv2.overlap ? mtv1 : mtv2;
		}	
	}

	// Collision between two circles（两个圆形的碰撞）
	function circleCollidesWithCircle(c1, c2){
		var distance = Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2)),
			overlap = Math.abs(c1.radius + c2.radius) - distance;

		return overlap < 0 ? 
			   new MinimumTranslationVector(undefined, 0) : // 未发生碰撞
			   new MinimumTranslationVector(undefined, overlap); // 发生碰撞
	}

	// Collision between polygon and circle（多边形与圆形之间的碰撞）
	function polygonCollidesWithCircle(polygon, circle, closestPoint){
		var axes = polygon.getAxes(),
			closestPoint = getPolygonPointClosestToCircle(polygon, circle);

		// axes.push(getCircleAxis(polygon, circle, closestPoint)); // getCircleAxis ==> undefined

		return polygon.minimumTransformVector(axes, circle);
	}

	// Move the shape that's moving (shapeMoving) out of collision
	// 将两个相互碰撞的图形分离
	/*
		function separate(shapeMoving, mtv){
			var dx,
				dy,
				velocityMagnitude, // 速度量级？？？
				point;

			if(mtv.axis === undefined){ // 圆形
				point = new Point();
				velocityMagnitude = Math.sqrt(Math.pow(velocity.x, 2) + Math.pow(velocity.y, 2));
				point.x = velocity.x / velocityMagnitude;
				point.y = velocity.y / velocityMagnitude;

				mtv.axis = new Vector(point.x, point.y);
			}

			dx = mtv.axis.x * mtv.overlap;
			dy = mtv.axis.y * mtv.overlap;

			if((dx < 0 && velocity.x < 0) || (dx > 0 && velocity.x > 0)){
				dx = - dx;
			}

			if((dy < 0 && velocity.y < 0) || (dy > 0 && velocity.y > 0)){
				dy = - dy;
			}

			shapeMoving.move(dx, dy);
		}
	*/